package dipl.algorithm.math.fp.utility;

import dipl.algorithm.exception.ArgumentException;
import dipl.algorithm.exception.ArgumentNullException;
import dipl.algorithm.math.fp.curve.BezierCurve;
import dipl.algorithm.math.fp.primitive.Point2df;
import java.util.Random;

/**
 * Provides methods for generation of Bezier-curves
 */
public class BezierCurveFactory {

	//
	// PUBLIC METHODS
	//

	/**
	 * Generates a bezier curve representing a polynomial in bernstein-basis
	 * with given coefficients.
	 * @param coefficients
	 * @param xFirst x-coordinate of first control point generated
	 * @param xLast x-coordinate of last control point generated
	 * @return curve
	 */
	public static BezierCurve CreateBernsteinPolynomial( double[] coefficients, double xFirst, double xLast )
		throws ArgumentNullException, ArgumentException {
		if( coefficients == null )
			throw new ArgumentNullException();
		if( coefficients.length == 0 )
			throw new ArgumentException();
		if( xLast < xFirst )
			throw new ArgumentException( "xLast < xFirst not allowed!" );

		int n = coefficients.length;
		Point2df[] controls = new Point2df[n];
		double t = xFirst;
		double incr = (xLast-xFirst)/(double)(n-1);
		for( int i = 0; i < n; i++ ) {
			controls[i] = new Point2df( t, coefficients[i] );
			t += incr;
		}
		return new BezierCurve( controls );
	}

	/**
	 * Creates a random beziér curve
	 * @param rand
	 * @param minDegree
	 * @param maxDegree
	 * @return
	 */
	public static BezierCurve CreateRandom( Random rand, int minDegree, int maxDegree ) {
		return CreateRandom( rand, minDegree, maxDegree, -1.0, -1.0, 1.0, 1.0 );
	}

	/**
	 * Creates a random beziér curve
	 * @param rand
	 * @param minDegree
	 * @param maxDegree
	 * @param minX
	 * @param minY
	 * @param maxX
	 * @param maxY
	 * @return
	 */
	public static BezierCurve CreateRandom( Random rand, int minDegree, int maxDegree, double minX, double minY, double maxX, double maxY ) {
		double min = (double)(minDegree+1);
		double max = (double)(maxDegree+1)-min;
		Point2df[] controls = new Point2df[(int)(min+rand.nextDouble()*max)];
		for( int i = 0; i < controls.length; i++ ) {
			controls[i] = new Point2df(
				minX+(maxX-minX)*rand.nextDouble(),
				minY+(maxY-minY)*rand.nextDouble()
			);
		}
		BezierCurve ret = null;
		try { ret = new BezierCurve( controls ); } catch( Exception e ) {}
		return ret;
	}

	/**
	 * Generates a bezier curve representing a polynomial in bernstein-basis
	 * with random coefficients in parameter interval [0.0,1.0]
	 * @param rand
	 * @param degree
	 * @return polynom
	 */
	public static BezierCurve CreateRandomBernsteinPolynomial_01( Random rand, int degree )
		throws ArgumentNullException, ArgumentException {
		return CreateRandomBernsteinPolynomial( rand, 0.0, 1.0, degree, degree );
	}

	/**
	 * Generates a bezier curve representing a polynomial in bernstein-basis
	 * with random coefficients.
	 * @param rand 
	 * @param xFirst x-coordinate of first control point generated
	 * @param xLast x-coordinate of last control point generated
	 * @param minDegree
	 * @param maxDegree
	 * @return curve
	 */
	public static BezierCurve CreateRandomBernsteinPolynomial( Random rand, double xFirst, double xLast, int minDegree, int maxDegree )
		throws ArgumentNullException, ArgumentException {
		double min = (double)(minDegree+1);
		double max = (double)(maxDegree+1)-min;
		double[] coefficients = new double[(int)(min+rand.nextDouble()*max)];
		int n = coefficients.length;
		for( int i = 0; i < n; i++ ) {
			coefficients[i] = -1.0+rand.nextDouble()*2.0;
		}
		return CreateBernsteinPolynomial( coefficients, xFirst, xLast );
	}

	/**
	 * Generates straight line curve (all control points are collinear, if minDegree > 1)
	 * @param rand
	 * @param minDegree
	 * @param maxDegree
	 * @param startX
	 * @param startY
	 * @return
	 */
	public static BezierCurve CreateStraightLine( Random rand, int minDegree, int maxDegree, double startX, double startY ) {
		double mx, my; // slopes
		Point2df[] controls;
		double min = minDegree+1;
		double max = (maxDegree+1)-(minDegree+1);
		controls = new Point2df[(int)(min+rand.nextDouble()*max)];
		double x = startX+rand.nextDouble()*2.0;
		double y = startY+rand.nextDouble()*2.0;
		controls[0] = new Point2df( x, y );
		mx = rand.nextDouble() <= 0.5 ? -rand.nextDouble()*0.5 : rand.nextDouble()*0.5;
		my = rand.nextDouble() <= 0.5 ? -rand.nextDouble()*0.5 : rand.nextDouble()*0.5;
		for( int i = 1; i < controls.length; i++ ) {
			controls[i] = new Point2df( x+mx*(double)(i), y+my*(double)(i) );
		}
		BezierCurve ret = null;
		try {
			ret = new BezierCurve( controls );
		} catch( Exception e ) {
			e.printStackTrace();
		}
		return ret;
	}

	/**
	 * Generates a bezier curve representing a segment of the x-axis
	 * @param l lower bound of segment
	 * @param u upper bound of segment
	 * @param degree degree of segment (>=1)
	 * @return curve
	 */
	public static BezierCurve CreateXAxisSegment( double l, double u, int degree ) throws ArgumentException {
		if( degree < 1 )
			throw new ArgumentException();
		double[] coefficients = new double[degree+1];
		for( int i = 0; i <= degree; i++ ) {
			coefficients[i] = 0.0;
		}
		BezierCurve res = null;
		try {
			res = CreateBernsteinPolynomial( coefficients, l, u );
		}
		catch( Exception e ) {
			e.printStackTrace();
		}
		return res;
	}
}
